#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Convert a TTF font (PingFangSC-Regular.ttf) into a compact cFONT bitmap table
compatible with GUI_Paint's Paint_DrawString_CN (GB2312) for e-paper.

Usage examples:
  python3 tools/ttf_to_cfont_pingfang.py \
      --ttf main/ePaper/lib/PingFangSC-Regular.ttf \
      --size 24 \
      --text "站在巨人肩膀上" \
      --out-c main/ePaper/lib/Fonts/pingfang24cn.c \
      --out-h main/ePaper/lib/Fonts/pingfang_cn.h \
      --sample-text-h main/ePaper/lib/Fonts/pf_sample_text.h \
      --y-offset 2

Dependencies:
  - Python 3.8+
  - Pillow (pip install pillow)

Notes:
  - This script generates a fixed-width glyph bitmap (Width=Height=size) for all Chinese characters.
  - It encodes the provided text to GB2312 and uses those bytes as CH_CN.index[2],
    which GUI_Paint expects when calling Paint_DrawString_CN.
  - Characters not encodable in GB2312 will be skipped and warned.
  - The output matrix packs bits MSB-first per byte, row-major, matching GUI_Paint.c expectations.
"""

import argparse
import os
import sys
from typing import List, Tuple

try:
    from PIL import Image, ImageDraw, ImageFont
except Exception as e:
    print("ERROR: Pillow not installed. Please run: pip install pillow", file=sys.stderr)
    raise

def gb2312_bytes(ch: str) -> Tuple[int, int]:
    b = ch.encode('gb2312', errors='strict')
    if len(b) != 2:
        raise ValueError(f"Character '{ch}' does not encode to 2-byte GB2312 (len={len(b)})")
    return b[0], b[1]

def render_glyph(ch: str, font: ImageFont.FreeTypeFont, width: int, height: int, y_offset: int) -> Image:
    # Create blank mono image
    img = Image.new('L', (width, height), color=0)
    draw = ImageDraw.Draw(img)
    # Measure glyph bbox on a larger canvas
    tmp = Image.new('L', (width*2, height*2), color=0)
    tdraw = ImageDraw.Draw(tmp)
    tdraw.text((0, 0), ch, fill=255, font=font)
    bbox = tmp.getbbox()  # returns None if empty
    if not bbox:
        bbox = (0, 0, 0, 0)
    gw = bbox[2] - bbox[0]
    gh = bbox[3] - bbox[1]
    # Render glyph centered horizontally, and vertically with controllable offset
    x = max(0, (width - gw)//2)
    base_y = max(0, (height - gh)//2)
    y = max(0, min(height - gh, base_y + y_offset))
    draw.text((x, y), ch, fill=255, font=font)
    return img

def pack_bitmap_to_bytes(img: Image.Image) -> bytes:
    # Ensure size
    w, h = img.size
    # Threshold to binary
    bw = img.point(lambda v: 255 if v >= 128 else 0, mode='1')
    # Pack bits: MSB=leftmost pixel, per row, pad to full bytes
    out = bytearray()
    for y in range(h):
        byte = 0
        bitpos = 7
        for x in range(w):
            pixel_on = bw.getpixel((x, y)) == 255
            if pixel_on:
                byte |= (1 << bitpos)
            bitpos -= 1
            if bitpos < 0:
                out.append(byte)
                byte = 0
                bitpos = 7
        if bitpos != 7:
            out.append(byte)
    return bytes(out)

def gen_cfont_table(chars: List[str], ttf_path: str, size: int, y_offset: int) -> Tuple[str, List[str]]:
    # Load font
    font = ImageFont.truetype(ttf_path, size=size)
    width = size
    height = size
    entries = []
    warnings = []
    for ch in chars:
        try:
            b0, b1 = gb2312_bytes(ch)
        except Exception as e:
            warnings.append(str(e))
            continue
        img = render_glyph(ch, font, width, height, y_offset)
        packed = pack_bitmap_to_bytes(img)
        # Sanity: expected bytes per glyph = rows * ceil(width/8)
        expected = height * ((width + 7)//8)
        if len(packed) != expected:
            warnings.append(f"Packed bytes mismatch for '{ch}': got {len(packed)}, expected {expected}")
        # Produce C initializer for CH_CN {index[2], matrix[...]} with hex bytes
        idx_str = f"0x{b0:02X}, 0x{b1:02X}"
        matrix_bytes = ', '.join(f"0x{b:02X}" for b in packed)
        entry = f"  {{ {{ {idx_str} }}, {{ {matrix_bytes} }} }}"
        entries.append(entry)
    # Build C source content
    c_lines = []
    c_lines.append("#include \"fonts.h\"")
    c_lines.append("// Auto-generated from TTF: PingFang SC, GB2312 subset")
    c_lines.append(f"// Vertical offset applied to glyph placement: y_offset={y_offset} px")
    c_lines.append(f"static const CH_CN PF24_Table[] = {{")
    c_lines.extend([e + ',' for e in entries])
    c_lines.append("};")
    c_lines.append("")
    c_lines.append("// ASCII width hint (not used for Chinese, set to size/2)\n")
    c_lines.append("// Width and Height are fixed for the table")
    c_lines.append(f"const cFONT PingFang24CN = {{\n    .table = PF24_Table,\n    .size = sizeof(PF24_Table)/sizeof(PF24_Table[0]),\n    .ASCII_Width = {max(8, size//2)},\n    .Width = {width},\n    .Height = {height}\n}};\n")
    return '\n'.join(c_lines), warnings

def gen_header() -> str:
    return (
        "#pragma once\n"
        "#include \"fonts.h\"\n"
        "// Generated PingFang SC cFONT declaration\n"
        "extern const cFONT PingFang24CN;\n"
    )

def gen_sample_text_header(text: str) -> str:
    # Emit GB2312-encoded bytes for provided text
    b = text.encode('gb2312', errors='ignore')
    arr = ', '.join(f"0x{byte:02X}" for byte in b)
    return (
        "#pragma once\n"
        "// GB2312-encoded sample text bytes for Paint_DrawString_CN\n"
        "static const char PF_SAMPLE_TEXT_GB2312[] = { " + arr + ", 0x00 };\n"
    )

def main():
    ap = argparse.ArgumentParser()
    ap.add_argument('--ttf', required=True)
    ap.add_argument('--size', type=int, default=24)
    ap.add_argument('--text', required=True, help='Chinese text to include (e.g., 站在巨人肩膀上)')
    ap.add_argument('--out-c', required=True)
    ap.add_argument('--out-h', required=True)
    ap.add_argument('--sample-text-h', required=False)
    ap.add_argument('--y-offset', type=int, default=2, help='Vertical offset to push glyphs downward within the cell (pixels)')
    args = ap.parse_args()

    chars = sorted(set(list(args.text)))
    c_content, warnings = gen_cfont_table(chars, args.ttf, args.size, args.y_offset)
    h_content = gen_header()
    st_content = gen_sample_text_header(args.text) if args.sample_text_h else None

    os.makedirs(os.path.dirname(args.out_c), exist_ok=True)
    with open(args.out_c, 'w', encoding='utf-8') as f:
        f.write(c_content)
    os.makedirs(os.path.dirname(args.out_h), exist_ok=True)
    with open(args.out_h, 'w', encoding='utf-8') as f:
        f.write(h_content)
    if args.sample_text_h:
        os.makedirs(os.path.dirname(args.sample_text_h), exist_ok=True)
        with open(args.sample_text_h, 'w', encoding='utf-8') as f:
            f.write(st_content)

    if warnings:
        print("Warnings:")
        for w in warnings:
            print(" - ", w)
    print("Done. Generated:")
    print("  ", args.out_c)
    print("  ", args.out_h)
    if args.sample_text_h:
        print("  ", args.sample_text_h)

if __name__ == '__main__':
    main()
